﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.IO;
using System.ServiceModel;
using System.Collections;

namespace PowerTriggersWcf
{
    [DataContract(Namespace = "http://PWTWcf")]
    public enum TaskTypes
    {
        [EnumMember]
        Service,
        [EnumMember]
        Device,
        [EnumMember]
        StartProcess,
        [EnumMember]
        StopProcess,
    }

    [DataContract(Namespace = "http://PWTWcf")]
    public enum TaskPropertyNames
    {
        [EnumMember]
        Action,
        [EnumMember]
        AppliesTo,
        [EnumMember]
        SupportsOther,
    }

    [DataContract(Namespace = "http://PWTWcf")]
    public enum TaskPropertyChoiceTypes
    {
        [EnumMember]
        Any,
        [EnumMember]
        ServiceAccount,
        [EnumMember]
        AllLoggedOnUsernames,
        [EnumMember]
        SpecifiedLoggedOnUsername,
        [EnumMember]
        SpecifiedUsername,
        [EnumMember]
        Yes,
        [EnumMember]
        No,
        [EnumMember]
        Restart,
        [EnumMember]
        Start,
        [EnumMember]
        Stop,
        [EnumMember]
        ReEnable,
        [EnumMember]
        Enable,
        [EnumMember]
        Disable,
        [EnumMember]
        WaitForIdle,
        [EnumMember]
        NoWait,
        [EnumMember]
        WaitForExit,
        [EnumMember]
        Pause,
    }

    [DataContract(Namespace = "http://PWTWcf")]
    public enum TaskPropertyAdditionalDataTypes
    {
        [EnumMember]
        None,
        [EnumMember]
        Username,
        [EnumMember]
        Milliseconds,
    }

    [DataContract(Namespace = "http://PWTWcf")]
    [KnownType(typeof(TaskService))]
    [KnownType(typeof(TaskDevice))]
    [KnownType(typeof(TaskStartProcess))]
    [KnownType(typeof(TaskStopProcess))]
    [KnownType(typeof(TaskTypes))]
    public abstract class Task
    {
        private static IDictionary<TaskTypes, IList<TaskProperty>> defaultProperties = new Dictionary<TaskTypes, IList<TaskProperty>>();

        private string description;
        private int sequence;
        private TaskTypes taskType;
        private TaskProperty[] properties;

        static Task()
        {
            List<TaskProperty> props;

            // Service
            props = new List<TaskProperty>(1);
            props.Add(new TaskProperty(TaskPropertyNames.Action
                                    , new TaskPropertyChoice[3]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Restart),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Start),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Stop),
                                        }
                                    ));
            defaultProperties.Add(TaskTypes.Service, props);

            // Device
            props = new List<TaskProperty>(1);
            props.Add(new TaskProperty(TaskPropertyNames.Action
                                    , new TaskPropertyChoice[3]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.ReEnable, "Re-enable"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Enable),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Disable),
                                        }
                                    ));
            defaultProperties.Add(TaskTypes.Device, props);

            // StopProcess
            props = new List<TaskProperty>(3);
            props.Add(new TaskProperty(TaskPropertyNames.Action
                                    , new TaskPropertyChoice[3]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.NoWait, "No Wait"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.WaitForExit, "Wait For Exit"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Pause,"Pause...", TaskPropertyAdditionalDataTypes.Milliseconds, null),
                                        }
                                    ));
            props.Add(new TaskProperty(TaskPropertyNames.SupportsOther
                                    , new TaskPropertyChoice[1]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Yes),
                                        }
                                    ));
            props.Add(new TaskProperty(TaskPropertyNames.AppliesTo
                                    , new TaskPropertyChoice[3]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Any),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.AllLoggedOnUsernames,"All Logged On Users"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.SpecifiedUsername,"Specific Username...", TaskPropertyAdditionalDataTypes.Username, new string[2] {string.Empty,string.Empty}),
                                        }
                                    ));
            defaultProperties.Add(TaskTypes.StopProcess, props);

            // StartProcess
            props = new List<TaskProperty>(3);
            props.Add(new TaskProperty(TaskPropertyNames.Action
                                    , new TaskPropertyChoice[4]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.NoWait, "No Wait"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.WaitForExit, "Wait For Exit"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.WaitForIdle, "Wait For Idle"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.Pause,"Pause...", TaskPropertyAdditionalDataTypes.Milliseconds, null),
                                        }
                                    ));
            props.Add(new TaskProperty(TaskPropertyNames.SupportsOther
                                    , new TaskPropertyChoice[1]
                                        {
                                           new TaskPropertyChoice(TaskPropertyChoiceTypes.Yes),
                                        }
                                    ));
            props.Add(new TaskProperty(TaskPropertyNames.AppliesTo
                                    , new TaskPropertyChoice[3]
                                        {
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.AllLoggedOnUsernames,"All Logged On Users"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.ServiceAccount, "Service Account"),
                                            new TaskPropertyChoice(TaskPropertyChoiceTypes.SpecifiedLoggedOnUsername,"Specific Username...", TaskPropertyAdditionalDataTypes.Username, new string[2] {string.Empty,string.Empty}),
                                        }
                                    ));
            defaultProperties.Add(TaskTypes.StartProcess, props);
        }


        private Task()
        {
        }

        protected Task(TaskTypes taskType, bool assignDefaultProperties)
        {
            this.TaskType = taskType;
            if (assignDefaultProperties)
            {
                this.Properties = Task.GetDefaultTaskPropertiesClone(taskType);
            }
        }

        internal Task Clone()
        {
            Task clone = Task.CreateTask(this.TaskType, this.description, this.ID, this.Status, this.Category, this.sequence, false);

            clone.properties = this.properties.ToArray();

            return clone;
        }

        internal static IDictionary<TaskTypes, IList<TaskProperty>> DefaultProperties
        {
            get { return defaultProperties; }
        }

        internal static TaskProperty[] GetDefaultTaskPropertiesClone(TaskTypes taskType)
        {
            List<TaskProperty> defaultProps = DefaultProperties[taskType].ToList();

            TaskProperty[] clonedProps = new TaskProperty[defaultProps.Count];
            int index = 0;
            foreach (var prop in defaultProps)
            {
                clonedProps[index] = new TaskProperty(prop);
                index++;
            }

            return clonedProps;
        }

        [DataMember()]
        public TaskTypes TaskType { get { return taskType; } private set { taskType = value; } }

        [DataMember()]
        public string Description
        {
            get { return description; }
            protected set { description = value; }
        }

        [DataMember()]
        public int Sequence
        {
            get { return sequence; }
            set { sequence = value; }
        }

        [DataMember()]
        public TaskProperty[] Properties
        {
            get { return properties.ToArray(); }
            protected set { properties = value; }
        }

        [DataMember()]
        public string Status { get; set; }

        [DataMember()]
        public string ID { get; set; }

        [DataMember()]
        public string Category { get; set; }

        internal static Task CreateTask(XElement xElement)
        {
            if (((XElement)xElement).Attribute("TaskType") != null)
            {
                string taskTypeText = xElement.Attribute("TaskType").Value;
                TaskTypes taskType = (TaskTypes)Enum.Parse(typeof(TaskTypes), taskTypeText, true);

                Task task = CreateTask(taskType, false);
                task.DeSerialize(xElement);

                return task;
            }
            else
                return null;
        }

        internal static Task CreateTask(TaskTypes taskType
                                    , string description
                                    , string ID
                                    , string status
                                    , string category
                                    , int sequence
                                    , bool createDefaultProperties)
        {
            Task task = CreateTask(taskType, true);

            task.Description = description ?? string.Empty;
            task.Status = status ?? string.Empty;
            task.ID = ID ?? string.Empty;
            task.Category = category ?? string.Empty;
            task.Sequence = sequence;

            return task;
        }

        private static Task CreateTask(TaskTypes taskType, bool assignDefaultProperties)
        {
            Task task;
            switch (taskType)
            {
                case TaskTypes.Service:
                    task = new TaskService(assignDefaultProperties);
                    break;
                case TaskTypes.Device:
                    task = new TaskDevice(assignDefaultProperties);
                    break;
                case TaskTypes.StartProcess:
                    task = new TaskStartProcess(assignDefaultProperties);
                    break;
                case TaskTypes.StopProcess:
                    task = new TaskStopProcess(assignDefaultProperties);
                    break;
                default:
                    throw new NotSupportedException("Creation of tasktype " + taskType.ToString() + " is not supported");
            }

            return task;
        }

        internal XElement Serialize()
        {
            XElement root = new XElement("Task");
            root.SetAttributeValue("Type", this.GetType().FullName);
            root.SetAttributeValue("TaskType", this.TaskType.ToString());
            root.SetAttributeValue("Sequence", this.Sequence);

            root.Add(new XElement("Description", this.Description));
            root.Add(new XElement("Status", this.Status));
            root.Add(new XElement("ID", this.ID));
            root.Add(new XElement("Category", this.Category));
            if (this.Properties != null)
            {
                XElement topElem = new XElement("Properties");
                foreach (TaskProperty property in properties)
                {
                    XElement propElem = new XElement("Property");
                    propElem.SetAttributeValue("Name", property.Name.ToString());
                    propElem.Add(new XElement("SelectedChoice", property.SelectedChoice.ToString()));

                    XElement choicesElem = new XElement("Choices");
                    foreach (TaskPropertyChoice choice in property.Choices)
                    {
                        choicesElem.Add(choice.Serialize("Choice"));
                    }
                    propElem.Add(choicesElem);

                    topElem.Add(propElem);
                }
                root.Add(topElem);
            }

            return new XElement(root);
        }

        internal void DeSerialize(XElement xElement)
        {
            this.Description = xElement.Descendants("Description").Select(x => x.Value).FirstOrDefault() ?? string.Empty;
            this.Status = xElement.Descendants("Status").Select(x => x.Value).FirstOrDefault() ?? string.Empty;
            this.ID = xElement.Descendants("ID").Select(x => x.Value).FirstOrDefault() ?? string.Empty;
            this.Category = xElement.Descendants("Category").Select(x => x.Value).FirstOrDefault() ?? string.Empty;
            this.Sequence = Int32.Parse(xElement.Attribute("Sequence").Value);
            this.Properties = GetDefaultTaskPropertiesClone(this.TaskType);
            if (xElement.Descendants("Properties").Count() > 0)
            {
                var list = xElement.Descendants("Properties").First().Descendants("Property")
                                    .OfType<XElement>()
                                    .ToList();
                Dictionary<TaskPropertyNames, TaskProperty> props = this.Properties.ToDictionary(prop => prop.Name, prop => prop);
                foreach (var xPropElem in list)
                {
                    string name = xPropElem.Attribute("Name") != null ? xPropElem.Attribute("Name").Value : null;
                    if (name != null)
                    {
                        XElement xSelectedChoice = xPropElem.Descendants("SelectedChoice").FirstOrDefault();
                        List<XElement> xChoices = xPropElem.Descendants("Choice").ToList();

                        TaskPropertyNames propertyName = (TaskPropertyNames)Enum.Parse(typeof(TaskPropertyNames), name, true);

                        TaskProperty defaultProperty = GetDefaultTaskPropertiesClone(this.TaskType).Where(prop => prop.Name == propertyName).First();

                        TaskPropertyChoiceTypes selectedChoice;
                        List<TaskPropertyChoice> choices;
                        if (xSelectedChoice == null)
                        {
                            selectedChoice = defaultProperty.SelectedChoice;
                        }
                        else
                        {
                            try
                            {
                                selectedChoice = (TaskPropertyChoiceTypes)Enum.Parse(typeof(TaskPropertyChoiceTypes), xSelectedChoice.Value, true);
                            }
                            catch (System.Exception ex)
                            {
                                EventLogHelper.LogError("Loading SelectedChoice", ex);
                                selectedChoice = defaultProperty.SelectedChoice;
                            }
                        }

                        choices = defaultProperty.Choices.ToList();
                        foreach (XElement xChoice in xChoices)
                        {
                            try
                            {
                                TaskPropertyChoice propertyChoice = new TaskPropertyChoice(xChoice, defaultProperty.Choices);

                                int choiceIndex = choices.IndexOf(choices.Where(ch => ch.ChoiceType == propertyChoice.ChoiceType).FirstOrDefault());
                                if (choiceIndex >= 0)
                                {
                                    choices[choiceIndex] = propertyChoice;
                                }
                                else
                                {
                                    choices.Add(propertyChoice);
                                }
                            }
                            catch (System.Exception ex)
                            {
                                EventLogHelper.LogError("Loading Choice", ex);
                            }
                        }

                        TaskProperty property = new TaskProperty(propertyName, choices.ToArray(), selectedChoice);
                        if (props.ContainsKey(propertyName))
                        {
                            props[propertyName] = property;
                        }
                        else
                        {
                            props.Add(propertyName, property);
                        }
                    }
                }

                this.Properties = props.Values.ToArray();
            }
        }

        protected TaskPropertyChoice GetSelectedChoice()
        {
            TaskPropertyChoice choice = (from prop in this.Properties
                                         from ch in prop.Choices
                                         where prop.Name == TaskPropertyNames.AppliesTo
                                            && ch.ChoiceType == prop.SelectedChoice
                                         select ch)
                                         .First();

            return choice;
        }

        protected TaskPropertyChoice GetSelectedAction()
        {
            TaskPropertyChoice choice = (from prop in this.Properties
                                         from ch in prop.Choices
                                         where prop.Name == TaskPropertyNames.Action
                                            && ch.ChoiceType == prop.SelectedChoice
                                         select ch)
                                         .First();

            return choice;
        }

        internal abstract bool PerformAction(HistoryHeader historyHeader);
    }

    [DataContract(Namespace = "http://PWTWcf")]
    public class TaskProperty
    {
        private TaskProperty() { }

        internal TaskProperty(TaskPropertyNames name, TaskPropertyChoice[] choices)
        {
            Name = name;
            Choices = choices;
            SelectedChoice = choices.Select(choice => choice.ChoiceType).FirstOrDefault();
        }

        internal TaskProperty(TaskPropertyNames name, TaskPropertyChoice[] choices, TaskPropertyChoiceTypes selectedChoice)
        {
            Name = name;
            Choices = choices;
            SelectedChoice = selectedChoice;
        }

        internal TaskProperty(TaskProperty task)
        {
            Name = task.Name;
            Choices = task.GetChoicesClone();
            SelectedChoice = task.SelectedChoice;
        }

        [DataMember()]
        public TaskPropertyNames Name { get; private set; }

        [DataMember()]
        public TaskPropertyChoice[] Choices { get; private set; }

        [DataMember()]
        public TaskPropertyChoiceTypes SelectedChoice { get; set; }

        private TaskPropertyChoice[] GetChoicesClone()
        {
            List<TaskPropertyChoice> choices = this.Choices.ToList();

            List<TaskPropertyChoice> clonedChoices = new List<TaskPropertyChoice>(choices.Count);
            foreach (var choice in choices)
            {
                clonedChoices.Add(new TaskPropertyChoice(choice));
            }

            return clonedChoices.ToArray();
        }
    }

    [DataContract(Namespace = "http://PWTWcf")]
    [KnownType(typeof(string[]))] // the AdditionalData field can contain an array of strings to hold the username/password
    public class TaskPropertyChoice
    {
        private TaskPropertyChoice() { }

        internal TaskPropertyChoice(XElement xElement, TaskPropertyChoice[] defaultPropertyChoices)
        {
            XElement xChoiceType = xElement.Descendants("ValueType").FirstOrDefault() ?? xElement.Descendants("ChoiceType").FirstOrDefault();
            XElement xDisplayText = xElement.Descendants("DisplayText").FirstOrDefault();
            XElement xAdditionalDataType = xElement.Descendants("AdditionalDataType").FirstOrDefault();
            XElement xAdditionalData = xElement.Descendants("AdditionalData").FirstOrDefault();

            TaskPropertyChoiceTypes choiceType = (TaskPropertyChoiceTypes)Enum.Parse(typeof(TaskPropertyChoiceTypes), xChoiceType.Value, true);
            TaskPropertyChoice defaultPropertyChoice = defaultPropertyChoices.Where(ch => ch.ChoiceType == choiceType).FirstOrDefault();

            string displayText;
            if (xDisplayText == null || string.IsNullOrEmpty(xDisplayText.Value))
            {
                displayText = defaultPropertyChoice == null ? choiceType.ToString() : defaultPropertyChoice.DisplayText;
            }
            else
            {
                displayText = xDisplayText.Value;
            }
            TaskPropertyAdditionalDataTypes additionalDataType = (TaskPropertyAdditionalDataTypes)Enum.Parse(typeof(TaskPropertyAdditionalDataTypes), xAdditionalDataType.Value, true);

            ChoiceType = choiceType;
            DisplayText = displayText;
            AdditionalDataType = additionalDataType;

            if (additionalDataType == TaskPropertyAdditionalDataTypes.None || xAdditionalData == null)
            {
                AdditionalData = null;
            }
            else
            {
                Type type = Type.GetType(xAdditionalData.Attribute("Type") == null ? "System.String" : xAdditionalData.Attribute("Type").Value);
                if (xAdditionalData.Attribute("ElementType") != null)
                {
                    Type elementType = Type.GetType(xAdditionalData.Attribute("ElementType").Value);

                    ArrayList arrayList = new ArrayList();

                    foreach (var xElem in xAdditionalData.Descendants("Element"))
                    {
                        arrayList.Add(Convert.ChangeType(xElem.Value, elementType));
                    }
                    AdditionalData = arrayList.ToArray(elementType);
                }
                else
                {
                    AdditionalData = Convert.ChangeType(xAdditionalData.Value, type);
                }
            }
        }

        internal TaskPropertyChoice(TaskPropertyChoiceTypes choiceType)
            : this(choiceType, choiceType.ToString())
        {
        }

        internal TaskPropertyChoice(TaskPropertyChoiceTypes choiceType, string displayText)
        {
            ChoiceType = choiceType;
            DisplayText = displayText;
            AdditionalDataType = TaskPropertyAdditionalDataTypes.None;
            AdditionalData = null;
        }

        internal TaskPropertyChoice(TaskPropertyChoiceTypes choiceType, string displayText, TaskPropertyAdditionalDataTypes additionalDataType, object additionalData)
        {
            ChoiceType = choiceType;
            DisplayText = displayText;
            AdditionalDataType = additionalDataType;
            AdditionalData = additionalData;
        }

        internal TaskPropertyChoice(TaskPropertyChoice choice)
        {
            ChoiceType = choice.ChoiceType;
            DisplayText = choice.DisplayText;
            AdditionalDataType = choice.AdditionalDataType;
            AdditionalData = choice.AdditionalData;
        }

        [DataMember()]
        public TaskPropertyChoiceTypes ChoiceType { get; private set; }

        [DataMember()]
        public string DisplayText { get; private set; }

        [DataMember()]
        public TaskPropertyAdditionalDataTypes AdditionalDataType { get; private set; }

        [DataMember()]
        public object AdditionalData { get; set; }

        internal XElement Serialize(string elementName)
        {
            XElement choiceElem = new XElement(elementName);
            choiceElem.Add(new XElement("ChoiceType", this.ChoiceType.ToString()));
            choiceElem.Add(new XElement("DisplayText", this.DisplayText));
            choiceElem.Add(new XElement("AdditionalDataType", this.AdditionalDataType.ToString()));
            if (this.AdditionalData != null)
            {
                XElement xAddData = new XElement("AdditionalData");
                xAddData.SetAttributeValue("Type", this.AdditionalData.GetType().FullName);
                if (this.AdditionalData is IList)
                {
                    IList list = (IList)this.AdditionalData;
                    Type type = list.AsQueryable().ElementType;

                    xAddData.SetAttributeValue("ElementType", type.FullName);

                    for (int index = 0; index < list.Count; index++)
                    {
                        XElement xElem = new XElement("Element", list[index]);
                        xElem.SetAttributeValue("Index", index);
                        xAddData.Add(xElem);
                    }

                }
                else
                {
                    xAddData.SetValue(this.AdditionalData);
                }
                choiceElem.Add(xAddData);
            }

            return choiceElem;
        }
    }
}
